package com.tolerate.managespringboot.Utils;

import com.jcraft.jsch.*;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.*;
import java.text.DecimalFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * SFTP工具类
 *
 * @author qq
 */
@Component
public class SftpUtils {

    private final Logger log = LoggerFactory.getLogger(SftpUtils.class);
    private ChannelSftp sftp;
    private Session sshSession;
    @Value("${sftp.host}")
    private String host;
    @Value("${sftp.port}")
    private int port;
    @Value("${sftp.username}")
    private String username;
    @Value("${sftp.password}")
    private String password;

    // Spring会保证带有@PostConstruct的方法在所有依赖注入完成后执行
//    @PostConstruct
    public void init() {
        if (port != 0) {
            try {
                connect(username, host, port, password);
                System.out.println("--------------sftp服务器连接成功！-------------");
                log.info("sftp服务器连接成功！");
            } catch (Exception e) {
                System.out.println("--------------sftp服务器连接失败！-------------");
                log.error("sftp服务器连接失败！{}", e.getMessage(), e);
            }
        } else {
            log.error("客户端未初始化");
        }
    }


    public ChannelSftp connect() {
        try {
            JSch jsch = new JSch();
            //获取sshSession  账号-ip-端口，从properties文件中获取连接ftp服务器的相关信息
            sshSession = jsch.getSession(username, host, port);

            //添加密码
            sshSession.setPassword(password);
            Properties sshConfig = new Properties();
            //严格主机密钥检查
            sshConfig.put("StrictHostKeyChecking", "no");

            sshSession.setConfig(sshConfig);
            //开启sshSession链接
            sshSession.connect();
            //获取sftp通道
            Channel channel = sshSession.openChannel("sftp");
            //开启
            channel.connect();
            sftp = (ChannelSftp) channel;
        } catch (JSchException e) {
            log.error(e.getMessage(), e);
        }
        return sftp;
    }

    /**
     * @param username 账号
     * @param host     ip
     * @param port     端口
     * @param password 密码
     * @return
     * @throws Exception
     */
    public ChannelSftp connect(String username, String host, int port, String password) throws Exception {
        JSch jsch = new JSch();
        //获取sshSession 账号-ip-端口，从properties文件中获取连接ftp服务器的相关信息
        sshSession = jsch.getSession(username, host, port);
        //添加密码
        sshSession.setPassword(password);
        Properties sshConfig = new Properties();
        //严格主机密钥检查
        sshConfig.put("StrictHostKeyChecking", "no");

        sshSession.setConfig(sshConfig);
        //开启sshSession链接
        sshSession.connect();
        //获取sftp通道
        Channel channel = sshSession.openChannel("sftp");
        //开启
        channel.connect();
        sftp = (ChannelSftp) channel;
        return sftp;
    }


    /**
     * 将本地的inputStream对象上传到目标服务器
     *
     * @param inputStream 本地输入流
     * @param dst         目录+文件名  /home/username/documents/remotefile.txt
     */
    public void uploadFile(InputStream inputStream, String dst) {
        try {
            sftp.put(inputStream, dst);
        } catch (SftpException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 将本地的inputStream对象上传到目标服务器，目标文件为dst，dst不能为目录
     *
     * @param inputStream 本地输入流
     * @param dst         目录+文件名  /home/username/documents/remotefile.txt
     * @param mode        传输模式
     *                    ChannelSftp.OVERWRITE 覆盖,ChannelSftp.RESUME 继续,ChannelSftp.APPEND 追加
     * @param monitor     自定义监控类,需实现SftpProgressMonitor接口,可以为null
     */
    public void uploadFile(InputStream inputStream, String dst, int mode, SftpProgressMonitor monitor) {
        try {
            if (sftp == null) {
                sftp = connect();
            }
            if (monitor == null) {
                sftp.put(inputStream, dst, mode);
            } else {
                sftp.put(inputStream, dst, monitor, mode);
            }
        } catch (SftpException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 将文件流写入远程Sftp中的某个文件
     *
     * @param remotePathAndName 远程文件目录+文件名   /home/username/documents/remotefile.txt
     * @param inputStream       文件流
     */
    public void writeInputStreamToSftp(String remotePathAndName, InputStream inputStream) {

        remotePathAndName = replaceAll(remotePathAndName);
        String path = remotePathAndName.substring(0, remotePathAndName.lastIndexOf(File.separator) + 1);
        String fileName = remotePathAndName.substring(remotePathAndName.lastIndexOf(File.separator) + 1);

        try {
            this.createDir(path);

            this.uploadFile(inputStream, fileName, 0, null);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private String replaceAll(String str) {
        str = str.replaceAll("\\\\", "\\" + File.separator);
        str = str.replaceAll("/", "\\" + File.separator);
        return str;
    }

    /**
     * 下载文件
     *
     * @param downloadFilePath 下载的文件完整目录
     * @param saveFile         保存在本地的文件路径
     * @return 返回的是文件
     */
    public File downloadFile(String downloadFilePath, String saveFile) {
        FileOutputStream fileOutputStream = null;
        try {
            int i = downloadFilePath.lastIndexOf('/');
            if (i == -1) {
                return null;
            }
            sftp.cd(downloadFilePath.substring(0, i));
            File file = new File(saveFile);
            fileOutputStream = new FileOutputStream(file);
            sftp.get(downloadFilePath.substring(i + 1), fileOutputStream);
            return file;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        } finally {
            sftp.disconnect();
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param filePath 文件全路径
     * @return
     */
    public boolean isFileExist(String filePath) {
        boolean isExit = false;
        try {
            // 使用 stat 方法来检查文件是否存在
            sftp.stat(filePath);
            isExit = true;
        } catch (SftpException e) {
            // 文件不存在，会出现异常
            isExit = false;
        }
        return isExit;
    }


    /**
     * 获取sftp服务器文件流
     *
     * @param downloadFilePath sftp文件上的文件全路径，包括具体文件名 /home/username/documents/remotefile.txt
     * @return 返回的是文件流
     */
    public InputStream download(String downloadFilePath) {
        if (sftp == null) {
            sftp = connect();
        }
        int i = downloadFilePath.lastIndexOf('/');
        InputStream inputStream = null;
        if (i == -1) {
            return null;
        }
        try {
            String path = downloadFilePath.substring(0, i);
            System.out.println(path);
            sftp.cd(downloadFilePath.substring(0, i));
            inputStream = sftp.get(downloadFilePath.substring(i + 1));
        } catch (SftpException e) {
            log.error("获取sftp服务文件流发生异常!", e);
        }
        return inputStream;
    }


    /**
     * 将文件流转为base64字节码
     *
     * @param in
     * @return
     * @throws Exception
     */
    public String inputStreamToBase64(InputStream in) throws Exception {
        byte[] bytes = IOUtils.toByteArray(in);
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 字符串对象转化为字节数组输入流
     *
     * @param content 字符串
     * @return 字节数组输入流
     */
    public ByteArrayInputStream toInputSteam(String content) {
        ByteArrayInputStream in = null;
        if (!StringUtils.isEmpty(content)) {
            try {
                in = new ByteArrayInputStream(content.getBytes("GBK"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
            }
        }

        return in;
    }

    /**
     * 在SFTP创建目录
     *
     * @param destinationPath 创建目录的完成路径
     */
    public void createDir(String destinationPath) {
        if (sftp == null) {
            sftp = connect();
        }
        String[] temp = destinationPath.replaceAll("\\\\", "/").split("/");
        StringBuilder tempDir = new StringBuilder();
        // String tempDir = "";
        if (destinationPath.startsWith("/")) {
            tempDir.append("/");
        }
        for (String s : temp) {
            tempDir.append(s).append("/");
            try {
                sftp.cd(tempDir.toString());
            } catch (Exception e) {
                try {
                    sftp.mkdir(tempDir.toString());
                    sftp.cd(tempDir.toString());
                } catch (SftpException e1) {
                    log.error("sftp创建文件路径失败，路径为:" + tempDir, e);
                }
            }
        }
    }

    /**
     * 删除sftp文件
     *
     * @param directPath 需要删除的文件目录
     * @param fileName   需要删除的文件
     * @throws Exception
     */
    public void deleteFile(String directPath, String fileName) throws Exception {
        //进入需要删除的文件目录下 
        sftp.cd(directPath);
        sftp.rm(fileName);
    }

    /**
     * 列出某目录下的文件名,只能查询到当前目录文件，拿不到子目录下的文件
     *
     * @param directory 需要列出的文件目录(sftp服务器目录下的)
     * @return 返回的文件名列表
     * @throws Exception
     */
    public List<String> listFiles(String directory) throws Exception {
        Vector<?> fileList;
        List<String> fileNameList = new ArrayList<>();
        fileList = sftp.ls(directory);
        for (Object o : fileList) {
            String fileName = ((ChannelSftp.LsEntry) o).getFilename();
            if (".".equals(fileName) || "..".equals(fileName)) {
                continue;
            }
            fileNameList.add(fileName);
        }
        return fileNameList;
    }

    /**
     * 拿到这个文件下的所有文件，包括子目录下的文件
     *
     * @param directory 远程文件目录  /home/username/documents
     * @return 文件列表
     * @throws Exception
     */
    public List<Map> listFilesByPath(String directory) throws Exception {
        Vector<?> fileList;
        List<Map> fileNameList = new ArrayList<>();
        fileList = sftp.ls(directory);
        for (Object o : fileList) {
            ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) o;
            SftpATTRS attrs = entry.getAttrs();

            Map map = new HashMap<>();
            String fullPath = directory + "/" + entry.getFilename();
            // 全路径
            map.put("fullPath", fullPath);
            // 文件名
            map.put("fileName", entry.getFilename());
            // 文件大小，转义
            map.put("fileSize", formatFileSizeMB(attrs.getSize()));
            // 最后修改时间
            map.put("lastModified", formatMTime(attrs.getMTime()));

            if (attrs.isDir()) {
                if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {
                    // 仅当遇到实际子目录时才递归调用
                    fileNameList.addAll(listFilesByPath(fullPath));
                } else {
                    // 对于"."和"..", 不执行任何操作
                    continue;
                }
            } else {
                // 添加非目录文件到集合
                fileNameList.add(map);
            }
        }
        return fileNameList;
    }


    /**
     * 通过文件完整路径从ftp上获取该，文件获取文件二进制流
     *
     * @param pathAndName 文件完整路径 例如:/test/aa.xml
     * @return byte数组
     */
    public byte[] getByteArrayFromSftp(String pathAndName) {
        InputStream is;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            pathAndName = replaceAll(pathAndName);
            String path = pathAndName.substring(0, pathAndName.lastIndexOf(File.separator) + 1);
            String fileName = pathAndName.substring(pathAndName.lastIndexOf(File.separator) + 1);

            createDir(path);

            // 获取远程ftp上指定文件的InputStream
            is = sftp.get(fileName);

            if (null == is) {
                throw new FileNotFoundException(fileName);
            }

            int length;
            byte[] buf = new byte[2048];
            while (-1 != (length = is.read(buf, 0, buf.length))) {
                bos.write(buf, 0, length);
            }
            is.close();

        } catch (Exception e) {
            log.error("通过文件完整路径从ftp上获取该文件的二进制流出错", e);
        }
        return bos.toByteArray();
    }

    /**
     * 断开SFTP Channel、Session连接
     */
    public void closeConnect() {
        if (this.sshSession.isConnected() || this.sshSession != null) {
            this.sshSession.disconnect();
        }
        sftp.quit();
        if (this.sftp != null) {
            if (sftp.isConnected()) {
                sftp.disconnect();
            }
        }
    }

    /**
     * 格式化文件大小，将字节数转换为MB
     *
     * @param fileSizeBytes
     * @return
     */
    public String formatFileSizeMB(long fileSizeBytes) {
        double fileSizeMB = fileSizeBytes / (1024.0 * 1024);
        DecimalFormat df = new DecimalFormat("#.##");
        return df.format(fileSizeMB) + " MB";
    }


    /* 将SftpATTRS中的MTime转换为格式化的日期字符串。
     *
     * @param attrs SftpATTRS对象，包含文件属性
     * @return 格式化的日期字符串，格式为"yyyy-MM-dd HH:mm:ss"
     */
    public String formatMTime(int attrsTime) {
        Instant instant = Instant.ofEpochSecond(attrsTime);
        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return dtf.format(zonedDateTime);
    }


    public Session getSshSession() {
        return sshSession;
    }

    public void setSshSession(Session sshSession) {
        this.sshSession = sshSession;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

}
